﻿using System;
using System.Runtime.Serialization;
using System.Security;
using Library.Collections.Generic;

namespace Library
{
    [Serializable]
    public class ArgumentTypeException : ArgumentException
    {
        private static void GenerateStrings(Type[] expectedTypes, out string types, out string interfaces, out uint n_types, out uint n_interfaces)
        {
            expectedTypes = expectedTypes ?? Type.EmptyTypes;

            System.Text.StringBuilder typesStringBuilder = new System.Text.StringBuilder();
            System.Text.StringBuilder interfacesStringBuilder = new System.Text.StringBuilder();
            
            uint typesCounter = 0;
            uint interfacesCounter = 0;
            
            uint n = (uint)expectedTypes.Length;
            if (n > 0)
            {
                if (expectedTypes[0] != null)
                {
                    if(expectedTypes[0].IsInterface)
                    {
                        interfacesStringBuilder.Append("'");
                        interfacesStringBuilder.Append(expectedTypes[0].FullName);
                        interfacesStringBuilder.Append("'");
                        ++interfacesCounter;
                    }
                    else
                    {
                        typesStringBuilder.Append("'");
                        typesStringBuilder.Append(expectedTypes[0].FullName);
                        typesStringBuilder.Append("'");
                        ++typesCounter;
                    }
                }
            }
            for (uint i = 1; i < n; ++i)
            {
                if (expectedTypes[i] != null)
                {
                    if (expectedTypes[i].IsInterface)
                    {
                        interfacesStringBuilder.Append(", '");
                        interfacesStringBuilder.Append(expectedTypes[i].FullName);
                        interfacesStringBuilder.Append("'");
                        ++interfacesCounter;
                    }
                    else
                    {
                        typesStringBuilder.Append(", '");
                        typesStringBuilder.Append(expectedTypes[i].FullName);
                        typesStringBuilder.Append("'");
                        ++typesCounter;
                    }
                }
            }
            n_types = typesCounter;
            n_interfaces = interfacesCounter;
            types = typesStringBuilder.ToString();
            interfaces = interfacesStringBuilder.ToString();
        }
        private static string GenerateMessage(string paramName, Type[] expectedTypes)
        {
            uint n_interfaces;
            uint n_types;

            string interfaces;
            string types;

            GenerateStrings(expectedTypes, out types, out interfaces, out n_types, out n_interfaces);

            string part1;
            if (paramName != null && paramName.Length > 0)
            {
                part1 = "Argument '" + paramName + "' ";
            }
            else
            {
                part1 = "An argument ";
            }

            if (n_interfaces > 1 && n_types > 1)
            {
                return part1 + "must be of types " + types + " and implement interfaces " + interfaces + ".";
            }
            else if (n_interfaces > 1 && n_types == 1)
            {
                return part1 + "must be of type " + types + " and implement interfaces " + interfaces + ".";
            }
            else if (n_interfaces > 1 && n_types == 0)
            {
                return part1 + "must implement interfaces " + interfaces + ".";
            }
            if (n_interfaces == 1 && n_types > 1)
            {
                return part1 + "must be of types " + types + " and implement interface " + interfaces + ".";
            }
            else if (n_interfaces == 1 && n_types == 1)
            {
                return part1 + "must be of type " + types + " and implement interface " + interfaces + ".";
            }
            else if (n_interfaces == 1 && n_types == 0)
            {
                return part1 + "must implement interface " + interfaces + ".";
            }
            if (n_interfaces == 0 && n_types > 1)
            {
                return part1 + "must be of types " + types + ".";
            }
            else if (n_interfaces == 0 && n_types == 1)
            {
                return part1 + "must be of type " + types + ".";
            }
            else if (n_interfaces == 0 && n_types == 0)
            {
                return part1 + "was not of an expected type or did not implement an expected interface.";
            }

            return default(string);
        }
        private static IVectorReadOnly<Type> Process(Type type)
        {
            List<Type> list = new List<Type>(1);
            if (type != null) list.Add(type);
            return ReadOnly.AsReadOnly(list);
        }

        public ArgumentTypeException()
            : base()
        {
            this.expectedTypes = ReadOnly.AsReadOnly(new Type[] { });
        }
        
        public ArgumentTypeException(string message)
            : base(message)
        {
            this.expectedTypes = ReadOnly.AsReadOnly(new Type[] { });
        }
        
        [SecuritySafeCritical]
        protected ArgumentTypeException(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            this.expectedTypes = ReadOnly.AsReadOnly((Type[])info.GetValue("ExpectedTypes", typeof(Type[])));
        }
        
        public ArgumentTypeException(string message, Exception innerException)
            : base(message, innerException)
        {
            this.expectedTypes = ReadOnly.AsReadOnly(new Type[] { });
        }

        public ArgumentTypeException(string paramName, Type expectedType)
            : base(GenerateMessage(paramName, new Type[] { expectedType }), paramName)
        {
            this.expectedTypes = Process(expectedType);
        }

        public ArgumentTypeException(string paramName, Type expectedType, Exception innerException)
            : base(GenerateMessage(paramName, new Type[] { expectedType }), paramName, innerException)
        {
            this.expectedTypes = Process(expectedType);
        }

        public ArgumentTypeException(string paramName, Type[] expectedTypes)
            : base(GenerateMessage(paramName, expectedTypes), paramName)
        {
            this.expectedTypes = ReadOnly.AsReadOnly(expectedTypes);
        }

        public ArgumentTypeException(string paramName, Type[] expectedTypes, Exception innerException)
            : base(GenerateMessage(paramName, expectedTypes), paramName, innerException)
        {
            this.expectedTypes = ReadOnly.AsReadOnly(expectedTypes);
        }

        public ArgumentTypeException(string message, string paramName, Type expectedType)
            : base(message ?? GenerateMessage(paramName, new Type[] { expectedType }), paramName)
        {
            this.expectedTypes = Process(expectedType);
        }

        public ArgumentTypeException(string message, string paramName, Type expectedType, Exception innerException)
            : base(message ?? GenerateMessage(paramName, new Type[] { expectedType }), paramName, innerException)
        {
            this.expectedTypes = Process(expectedType);
        }

        public ArgumentTypeException(string message, string paramName, Type[] expectedTypes)
            : base(message ?? GenerateMessage(paramName, expectedTypes), paramName)
        {
            this.expectedTypes = ReadOnly.AsReadOnly(expectedTypes);
        }

        public ArgumentTypeException(string message, string paramName, Type[] expectedTypes, Exception innerException)
            : base(message ?? GenerateMessage(paramName, expectedTypes), paramName, innerException)
        {
            this.expectedTypes = ReadOnly.AsReadOnly(expectedTypes);
        }

        IVectorReadOnly<Type> expectedTypes;
        public IVectorReadOnly<Type> ExpectedTypes
        {
            get
            {
                return expectedTypes;
            }
        }

        public override void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            base.GetObjectData(info, context);
            info.AddValue("ExpectedTypes", expectedTypes, typeof(Type[]));
        }
    }
}
